package org.pockito.dctmpersistence;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.pockito.dmspersistence.DmsEntity;
import org.pockito.dmspersistence.DmsPersistenceException;
import org.pockito.dmspersistence.DmsType;
import org.pockito.dmspersistence.Transient;

public class AnnotationManager {

	private Map<String, AnnotationInfo> annotationMap = new HashMap<String, AnnotationInfo>();

	public AnnotationManager() {
	}
	
	public AnnotationInfo getAnnotationInfo(Class<?> entityClass) {
		AnnotationInfo ai =  getAnnotationInfo(entityClass.getName());
		if (ai == null) {
			ai = addAnnotationInfo(entityClass);
		}
		return ai;
	}

	public AnnotationInfo getAnnotationInfo(Object entity) {
		return getAnnotationInfo(entity.getClass());
	}

	private AnnotationInfo getAnnotationInfo(String classname) {
		AnnotationInfo ai =  getAnnotationMap().get(classname);
		return ai;
	}

	public AnnotationInfo addAnnotationInfo(Class<?> c) {

		{
			DmsEntity entity = c.getAnnotation(DmsEntity.class);
			if (entity == null) {
				throw new DmsPersistenceException("Class not marked as an @Entity: " + c.getName());
			}
		}		
        AnnotationInfo ai = new AnnotationInfo();
        
        String dmsTypeName = null;

        Class<?> superClass = c;
        @SuppressWarnings("unused")
		Class<?> rootClass = null;
        while ((superClass = superClass.getSuperclass()) != null) {
            DmsEntity entity = (DmsEntity) superClass.getAnnotation(DmsEntity.class);
            if (entity != null) {
            	if (dmsTypeName == null) {
	                // get the underlying DMS type
	                DmsType dmsType = c.getAnnotation(DmsType.class);
	                if (dmsType == null || "".equals(dmsType.name())) {
	                    dmsTypeName = getTypeOfNativeEntity(entity.name());
	                } else {
	                	dmsTypeName = dmsType.name();
	                }
            	}
                addProperties(ai, superClass);
                addMethods(ai, superClass);
                rootClass = superClass;
            }
        }
        if (StringUtils.isBlank(dmsTypeName)) {
        	throw new DmsPersistenceException("You must specify a name for @DmsType on " + c.getName());
        }
        addProperties(ai, c);
        addMethods(ai, c);
        ai.setDmsType(dmsTypeName);
        getAnnotationMap().put(c.getName(), ai);
        // TODO: check model against DMS Model
		return ai;
	}
	
    private void addMethods(AnnotationInfo ai, Class<?> c) {
        Method[] methods = c.getDeclaredMethods();
        for (Method method : methods) {
            String methodName = method.getName();
            if (!methodName.startsWith("get")) continue;
            Transient transientM = method.getAnnotation(Transient.class);
            if (transientM != null) continue; // we don't save this one
            ai.addGetter(method);
        }
    }


	private void addProperties(AnnotationInfo ai, Class<?> c) {
        for (Field field : c.getDeclaredFields()) {
            parseProperty(ai, c, field);
        }
	}

	private void parseProperty(AnnotationInfo ai, Class<?> c, Field field) {
		if (!field.isAnnotationPresent(Transient.class)) {
			ai.addField(field);
		}
			
	}

	// TODO: this should be externalized
	public static String getTypeOfNativeEntity(String entityName) {
		if ("native.PersistedObject".equals(entityName)) {
			return "dm_sysobject";
		} else if ("native.Document".equals(entityName)) {
			return "dm_sysobject";
		} else if ("native.Folder".equals(entityName)) {
			return "dm_folder";
		}
		return null;
		
	}

	protected Map<String, AnnotationInfo> getAnnotationMap() {
		return annotationMap;
	}
}
